<!doctype html>
<html>
<head>
	<title>WaveShaperNode interface - Curve tests | WebAudio</title>

	<script type="text/javascript" src="../../../resources/testharness.js"></script>
	<script type="text/javascript" src="../../../resources/testharnessreport.js"></script>
	<script type="text/javascript" src="../../js/vendor-prefixes.js"></script>
</head>
<body>
	<div id="log">
	</div>

	<script type="text/javascript">
		var sampleRate=44100.0;
		var tolerance=0.01;

		/*
		Testing that -1, 0 and +1 map correctly to curve (with 1:1 correlation)
		=======================================================================
		From the specification:
			The input signal is nominally within the range -1 -> +1.
			Each input sample within this range will index into the shaping curve with a signal level of zero corresponding
				to the center value of the curve array.
		*/
		(function() {
			var threeElementCurve=[2.0, -3.0, 4.0];
			var inputData=[-1.0, 0, 1.0];
			var expectedData=[2.0, -3.0, 4.0];
			executeTest(threeElementCurve, inputData, expectedData, "Testing that -1, 0 and +1 map correctly to curve (with 1:1 correlation)");
		})();

		/*
		Testing interpolation (where inputs don't correlate directly to curve elements)
		===============================================================================
		From the specification:
			The implementation must perform linear interpolation between adjacent points in the curve.
		*/
		(function() {
			var threeElementCurve=[2.0, -3.0, 4.0];
			var inputData=[-0.5, +0.5, +0.75];
			var expectedData=[-0.5, +0.5, +2.25];
			executeTest(threeElementCurve, inputData, expectedData, "Testing interpolation (where inputs don't correlate directly to curve elements)");
		})();

		/*
		Testing out-of-range inputs (should be mapped to the first/last elements of the curve)
		======================================================================================
		From the specification:
			Any sample value less than -1 will correspond to the first value in the curve array.
			Any sample value greater than +1 will correspond to the last value in the curve array.
		*/
		(function() {
			var threeElementCurve=[2.0, -3.0, 4.0];
			var inputData=[-1.5, +1.5];
			var expectedData=[2.0, 4.0];
			executeTest(threeElementCurve, inputData, expectedData, "Testing out-of-range inputs (should be mapped to the first/last elements of the curve)");
		})();

		/*
		Testing a 2-element curve (does not have a middle element)
		==========================================================
		From the specification:
			Each input sample within this range will index into the shaping curve with a signal level of zero corresponding
				to the center value of the curve array.
			The implementation must perform linear interpolation between adjacent points in the curve.
		*/
		(function() {
			var twoElementCurve=[2.0, -2.0];
			var inputData=[-1.0, 0, 1.0];
			var expectedData=[2.0, 0.0, -2.0];
			executeTest(twoElementCurve, inputData, expectedData, "Testing a 2-element curve (does not have a middle element)");
		})();

		/*
		Testing a 4-element curve (does not have a middle element)
		==========================================================
		From the specification:
			Each input sample within this range will index into the shaping curve with a signal level of zero corresponding
				to the center value of the curve array.
			The implementation must perform linear interpolation between adjacent points in the curve.
		*/
		(function() {
			var fourElementCurve=[1.0, 2.0, 4.0, 7.0];
			var inputData=[-1.0, 0, 1.0];
			var expectedData=[1.0, 3.0, 7.0];
			executeTest(fourElementCurve, inputData, expectedData, "Testing a 4-element curve (does not have a middle element)");
		})();

		/*
		Testing a huge curve
		====================
		From the specification:
			Each input sample within this range will index into the shaping curve with a signal level of zero corresponding
				to the center value of the curve array.
		*/
		(function() {
			var bigCurve=[];
			for(var i=0;i<=60000;i++) { bigCurve.push(i/3.5435); }
			var inputData=[-1.0, 0, 1.0];
			var expectedData=[bigCurve[0], bigCurve[30000], bigCurve[60000]];
			executeTest(bigCurve, inputData, expectedData, "Testing a huge curve");
		})();

		/*
		Testing single-element curve (boundary condition)
		=================================================
		From the specification:
			Each input sample within this range will index into the shaping curve with a signal level of zero corresponding
				to the center value of the curve array.
			Any sample value less than -1 will correspond to the first value in the curve array.
			Any sample value greater than +1 will correspond to the last value in the curve array.
			The implementation must perform linear interpolation between adjacent points in the curve.
		Note:
			I found a post on the W3C audio mailing list (from one of the Chris's) that suggested it would be feasible
				to use the WaveShaperNode to create constant values.
		*/
		(function() {
			var oneElementCurve=[1.0];
			var inputData=[-1.0, 0, 1.0, -2.0, 2.0];
			var expectedData=[1.0, 1.0, 1.0, 1.0, 1.0];
			executeTest(oneElementCurve, inputData, expectedData, "Testing single-element curve (boundary condition)");
		})();

		/*
		Testing null curve (should return input values)
		===============================================
		From the specification:
			Initially the curve attribute is null, which means that the WaveShaperNode will pass its input to its output
				without modification.
		*/
		(function() {
			var inputData=[-1.0, 0, 1.0, 2.0];
			var expectedData=[-1.0, 0.0, 1.0, 2.0];
			executeTest(null, inputData, expectedData, "Testing null curve (should return input values)");
		})();

		/*
		Testing zero-element curve (unspecified result)
		===============================================
		From the specification:
			Unspecified result (I assume it will be treated in the same way as a null curve).
		Note:
			Mozilla test_waveShaperNoCurve.html indicates they expect same results as a null curve.
		*/
		(function() {
			var zeroElementCurve=[];
			var inputData=[-1.0, 0, 1.0, 2.0];
			var expectedData=[-1.0, 0.0, 1.0, 2.0];
			executeTest(zeroElementCurve, inputData, expectedData, "Testing zero-element curve (unspecified result)");
		})();


		/**
		* Function that does the actual testing (using an asynchronous test).
		* @param {?Array.<number>} curveData - Array containing values for the WaveShaper curve.
		* @param {!Array.<number>} inputData - Array containing values for the input stream.
		* @param {!Array.<number>} expectedData - Array containing expected results for each of the corresponding inputs.
		* @param {!string} testName - Name of the test case.
		*/
		function executeTest(curveData, inputData, expectedData, testName) {
			var stTest=async_test("WaveShaperNode - "+testName);

			// Create offline audio context.
			var ac=new OfflineAudioContext(1, inputData.length, sampleRate);

			// Create the WaveShaper and its curve.
			var waveShaper=ac.createWaveShaper();
			if(curveData!=null) {
				var curve=new Float32Array(curveData.length);
				for(var i=0;i<curveData.length;i++) { curve[i]=curveData[i]; }
				waveShaper.curve=curve;
			}
			waveShaper.connect(ac.destination);

			// Create buffer containing the input values.
			var inputBuffer=ac.createBuffer(1, Math.max(inputData.length, 2), sampleRate);
			var d=inputBuffer.getChannelData(0);
			for(var i=0;i<inputData.length;i++) { d[i]=inputData[i]; }

			// Play the input buffer through the WaveShaper.
			var src=ac.createBufferSource();
			src.buffer=inputBuffer;
			src.connect(waveShaper);
			src.start();

			// Test the outputs match the expected values.
			ac.oncomplete=function(ev) {
				var d=ev.renderedBuffer.getChannelData(0);

				stTest.step(function() {
					for(var i=0;i<expectedData.length;i++) {
						var curveText="null";
						if(curve!=null) {
							if(curveData.length<20) {
								curveText=curveData.join(",");
							} else {
								curveText="TooBigToDisplay ("+(curveData.length-1)+" elements)";
							}
						}
						var comment="Input="+inputData[i]+", Curve=["+curveText+"] >>> ";
						assert_approx_equals(d[i], expectedData[i], tolerance, comment);
					}
				});

				stTest.done();
			};
			ac.startRendering();
		}
	</script>
</body>
</html>
